#include "DocumentStreamWriter.h"

#include "Document.h"
#include "IDocumentObject.h"

#include <QDataStream>
#include <QDebug>
#include <QIODevice>
#include <QMetaObject>
#include <QMetaProperty>
#include <QPointF>
#include <QSizeF>
#include <QXmlStreamWriter>

using namespace LDO;

DocumentStreamWriter::DocumentStreamWriter(QObject *parent)
    : QObject(parent)
    , m_writer(new QXmlStreamWriter())
{
    m_writer->setAutoFormatting(true);
    m_writer->writeDefaultNamespace("https://libre-eda.org/xml-document/1.0");
}

void DocumentStreamWriter::setAutoFormatting(bool enabled)
{
    m_writer->setAutoFormatting(enabled);
}

void DocumentStreamWriter::setDevice(QIODevice *device)
{
    m_writer->setDevice(device);
}

QIODevice *DocumentStreamWriter::device() const
{
    return m_writer->device();
}

// FIXME: handle error while writing the document
bool DocumentStreamWriter::writeDocument(const Document *document)
{
    m_writer->writeStartDocument();
    m_writer->writeStartElement("document");

    for (int i = 0; i < document->childObjectCount(); i++)
    {
        const QString className(document->childObject(i)->metaObject()->className());
        m_writer->writeStartElement("object");
        m_writer->writeAttribute("class", pointerTypeNameForClass(className));
        writeDocumentObject(document->childObject(i));
        m_writer->writeEndElement();
    }

    m_writer->writeEndDocument();

    return !m_writer->hasError();
}

QString DocumentStreamWriter::errorString() const
{
    return "FIXME";
}

void DocumentStreamWriter::writeDocumentObject(const IDocumentObject *object)
{
    if (object == nullptr)
        return;

    writeDocumentObject(object->metaObject(), object);
}

void DocumentStreamWriter::writeDocumentObject(const QMetaObject *metaObject, const QObject *object)
{
    if (QString(metaObject->className()) == "LDO::IDocumentObject")
        return;

    if (metaObject->superClass() != nullptr)
        writeDocumentObject(metaObject->superClass(), object);

    for (int metaPropertyIndex = metaObject->propertyOffset();
         metaPropertyIndex < metaObject->propertyCount();
         metaPropertyIndex++)
    {
        QMetaProperty metaProperty = metaObject->property(metaPropertyIndex);

        if (!metaProperty.isDesignable() || !metaProperty.isReadable())
            continue;

        QVariant variantValue = metaProperty.read(object);
        if (!variantValue.isValid())
        {
            qWarning() << "DocumentStreamWriter::writeDocumentObject: Got an invalid QVariant while reading object property";
            continue;
        }

        writeProperty(metaProperty, variantValue);
    }
}

void DocumentStreamWriter::writeProperty(const QMetaProperty &metaProperty, const QVariant &variantValue)
{

    if (metaProperty.isFlagType())
        writeFlagValue(metaProperty, variantValue);
    else if (metaProperty.isEnumType())
        writeEnumValue(metaProperty, variantValue);
    else if (metaProperty.type() == QVariant::UserType)
        writeUserTypeValue(metaProperty, variantValue);
    else
        writeStandardTypeValue(metaProperty, variantValue);
}

QString DocumentStreamWriter::pointerTypeNameForClass(const QString &className)
{
    return QString("%1*").arg(className);
}



void DocumentStreamWriter::writeFlagValue(const QMetaProperty &metaProperty, const QVariant &variantValue)
{
    m_writer->writeStartElement("property");
    m_writer->writeAttribute("name", metaProperty.name());
    m_writer->writeAttribute("type", metaProperty.typeName());

    QMetaEnum metaEnum = metaProperty.enumerator();
    int intValue = QVariant(QVariant::Int, variantValue.data()).toInt();
    QStringList valueTexts;
    for (int i = 0; i < metaEnum.keyCount(); i++)
    {
        if (intValue & metaEnum.value(i))
            valueTexts.append(metaEnum.key(i));
    }

    //writeStartValue(metaProperty.typeName());
    writeText(valueTexts.join("|"));
    //writeEndValue();

    m_writer->writeEndElement();
}

void DocumentStreamWriter::writeEnumValue(const QMetaProperty &metaProperty, const QVariant &variantValue)
{
    m_writer->writeStartElement("property");
    m_writer->writeAttribute("name", metaProperty.name());
    m_writer->writeAttribute("type", metaProperty.typeName());

    QMetaEnum metaEnum = metaProperty.enumerator();
    int intValue = variantValue.toInt();

    //writeStartValue(metaProperty.typeName());
    writeText(metaEnum.valueToKey(intValue));
    //writeEndValue();

    m_writer->writeEndElement();
}

void DocumentStreamWriter::writeUserTypeValue(const QMetaProperty &metaProperty, const QVariant &variantValue)
{
    QMetaType propMetaType(metaProperty.userType());

    if (!propMetaType.isRegistered())
    {
        qWarning() << "DocumentStreamWriter::writeDocumentObject: Property has an unregistered QMetaType" << metaProperty.typeName();
    }
    const QMetaObject *propMetaObject = propMetaType.metaObject();
    if (propMetaObject != nullptr)
    {
        if (propMetaObject->inherits(&IDocumentObject::staticMetaObject))
        {
            const IDocumentObject *propertyValue = variantValue.value<IDocumentObject*>();
            if (propertyValue == nullptr)
                return;
            // Get the real pointed-to object class name
            // a property could be declared as "BaseClass*", but the property value might be "DerivedClass*"
            const QString className = propertyValue->metaObject()->className();
            m_writer->writeStartElement("property");
            m_writer->writeAttribute("name", metaProperty.name());
            m_writer->writeAttribute("type", pointerTypeNameForClass(className));
            writeDocumentObject(variantValue.value<IDocumentObject*>());
            m_writer->writeEndElement();
        }
        else
            qWarning() << "DocumentStreamWriter::writeDocumentObject: Unhandled UserType for" << metaProperty.typeName();
    }
    else
    {
        QList<IDocumentObject*> qobjectList;
        if (variantValue.canConvert(QMetaType::QVariantList)) {
            m_writer->writeStartElement("property");
            m_writer->writeAttribute("name", metaProperty.name());
            m_writer->writeAttribute("type", metaProperty.typeName());
            QSequentialIterable iterable = variantValue.value<QSequentialIterable>();
            foreach (const QVariant& item, iterable) {
                IDocumentObject* object = item.value<IDocumentObject*>();
                if (object)
                {
                    m_writer->writeStartElement("item");
                    writeDocumentObject(object);
                    m_writer->writeEndElement();
                    //writeEndValue();
                }
            }
            m_writer->writeEndElement();
        }
        else
        {
            qWarning() << "DocumentStreamWriter::writeDocumentObject: Property user-type has no meta-object" << metaProperty.typeName();
        }
    }
}

void DocumentStreamWriter::writeStandardTypeValue(const QMetaProperty &metaProperty, const QVariant &variant)
{
    switch (variant.type()) {
        case QVariant::Invalid:
            qWarning() << "writeStandardTypeValue" << "INVALID";
            break;
        case QVariant::Bool:
            m_writer->writeStartElement("property");
            m_writer->writeAttribute("name", metaProperty.name());
            m_writer->writeAttribute("type", metaProperty.typeName());
            writeText(variant.value<bool>() ? "True" : "False");
            m_writer->writeEndElement();
            break;
        case QVariant::Int:
            m_writer->writeStartElement("property");
            m_writer->writeAttribute("name", metaProperty.name());
            m_writer->writeAttribute("type", metaProperty.typeName());
            writeText(QString::number(variant.value<int>()));
            m_writer->writeEndElement();
            break;
//        case QVariant::UInt:
//            break;
//        case QVariant::LongLong:
//            break;
//        case QVariant::ULongLong:
//            break;
        case QVariant::Double:
            m_writer->writeStartElement("property");
            m_writer->writeAttribute("name", metaProperty.name());
            m_writer->writeAttribute("type", metaProperty.typeName());
            writeText(QString::number(variant.value<double>()));
            m_writer->writeEndElement();
            break;
//        case QVariant::Char:
//            break;
//        case QVariant::Map:
//            break;
//        case QVariant::List:
//            break;
        case QVariant::String:
            m_writer->writeStartElement("property");
            m_writer->writeAttribute("name", metaProperty.name());
            m_writer->writeAttribute("type", metaProperty.typeName());
            writeText(variant.value<QString>());
            m_writer->writeEndElement();
            break;
        case QVariant::StringList:
            m_writer->writeStartElement("property");
            m_writer->writeAttribute("name", metaProperty.name());
            m_writer->writeAttribute("type", metaProperty.typeName());
            for (const QString &str: variant.value<QStringList>())
            {
               m_writer->writeStartElement("item");
               m_writer->writeCharacters(str);
               m_writer->writeEndElement();
            }
            m_writer->writeEndElement();
            break;
//        case QVariant::ByteArray:
//            break;
//        case QVariant::BitArray:
//            break;
//        case QVariant::Date:
//            break;
//        case QVariant::Time:
//            break;
//        case QVariant::DateTime:
//            break;
//        case QVariant::Url:
//            break;
//        case QVariant::Locale:
//            break;
//        case QVariant::Rect:
//            break;
//        case QVariant::RectF:
//            break;
//        case QVariant::Size:
//            break;
        case QVariant::SizeF:
            m_writer->writeStartElement("property");
            m_writer->writeAttribute("name", metaProperty.name());
            m_writer->writeAttribute("type", metaProperty.typeName());
            m_writer->writeStartElement("width");
            m_writer->writeCharacters(QString::number(variant.value<QSizeF>().width()));
            m_writer->writeEndElement();
            m_writer->writeStartElement("height");
            m_writer->writeCharacters(QString::number(variant.value<QSizeF>().height()));
            m_writer->writeEndElement();
            m_writer->writeEndElement();
            break;
//        case QVariant::Line:
//            break;
//        case QVariant::LineF:
//            break;
//        case QVariant::Point:
//            break;
        case QVariant::PointF:
            m_writer->writeStartElement("property");
            m_writer->writeAttribute("name", metaProperty.name());
            m_writer->writeAttribute("type", metaProperty.typeName());
            m_writer->writeStartElement("x");
            m_writer->writeCharacters(QString::number(variant.value<QPointF>().x()));
            m_writer->writeEndElement();
            m_writer->writeStartElement("y");
            m_writer->writeCharacters(QString::number(variant.value<QPointF>().y()));
            m_writer->writeEndElement();
            m_writer->writeEndElement();
            break;
//        case QVariant::RegExp:
//            break;
//        case QVariant::RegularExpression:
//            break;
//        case QVariant::Hash:
//            break;
//        case QVariant::EasingCurve:
//            break;
//        case QVariant::Uuid:
//            break;
//        case QVariant::ModelIndex:
//            break;
//        case QVariant::PersistentModelIndex:
//            break;
//        case QVariant::LastCoreType:
//            break;
//        case QVariant::Font:
//            break;
//        case QVariant::Pixmap:
//            break;
//        case QVariant::Brush:
//            break;
//        case QVariant::Color:
//            break;
//        case QVariant::Palette:
//            break;
//        case QVariant::Image:
//            break;
//        case QVariant::Polygon:
//            break;
//        case QVariant::Region:
//            break;
//        case QVariant::Bitmap:
//            break;
//        case QVariant::Cursor:
//            break;
//        case QVariant::KeySequence:
//            break;
//        case QVariant::Pen:
//            break;
//        case QVariant::TextLength:
//            break;
//        case QVariant::TextFormat:
//            break;
//        case QVariant::Matrix:
//            break;
//        case QVariant::Transform:
//            break;
//        case QVariant::Matrix4x4:
//            break;
//        case QVariant::Vector2D:
//            break;
//        case QVariant::Vector3D:
//            break;
//        case QVariant::Vector4D:
//            break;
//        case QVariant::Quaternion:
//            break;
//        case QVariant::PolygonF:
//            break;
//        case QVariant::Icon:
//            break;
//        case QVariant::LastGuiType:
//            break;
//        case QVariant::SizePolicy:
//            break;
        case QVariant::UserType:
            qWarning() << "writeStandardTypeValue" << "USER";
            break;
//        case QVariant::LastType:
//            break;
        default:
            qWarning() << "writeStandardTypeValue" << "UNSUPPORTED";
            break;
    }
}

void DocumentStreamWriter::writeText(const QString &text)
{
    m_writer->writeCharacters(text);
}
